﻿/*
 * Copyright (c) 2015-2016 Constantijn Evenhuis (blueprints@const.nl)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
using Blueprints.IO;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Blueprints.Editing
{
    internal sealed class NodeCollection : IList<EditNode>
    {
        private readonly List<EditNode> nodes;

        public NodeCollection()
        {
            nodes = new List<EditNode>();
        }

        public NodeCollection(IEnumerable<EditNode> nodes)
        : this()
        {
            this.nodes.AddRange(nodes);
        }

        public int Count
        {
            get
            {
                return nodes.Count;
            }
        }

        bool ICollection<EditNode>.IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public EditNode this[int index]
        {
            get
            {
                return nodes[index];
            }
            set
            {
                EditNode previous = nodes[index];
                nodes[index] = value;
            }
        }

        public void Add(EditNode item)
        {
            nodes.Add(item);
        }

        public void Clear()
        {
            nodes.Clear();
        }

        public bool Contains(EditNode item)
        {
            return nodes.Contains(item);
        }

        void ICollection<EditNode>.CopyTo(EditNode[] array, int arrayIndex)
        {
            nodes.CopyTo(array, arrayIndex);
        }

        IEnumerator<EditNode> IEnumerable<EditNode>.GetEnumerator()
        {
            return nodes.GetEnumerator();
        }

        public int IndexOf(EditNode item)
        {
            return nodes.IndexOf(item);
        }

        public void Insert(int index, EditNode item)
        {
            nodes.Insert(index, item);
        }

        public void InsertRange(int index, IEnumerable<EditNode> items)
        {
            nodes.InsertRange(index, items);
        }

        public bool Remove(EditNode item)
        {
            return nodes.Remove(item);
        }

        public void RemoveAt(int index)
        {
            nodes.RemoveAt(index);
        }

        public void RemoveRange(int index, int count)
        {
            nodes.RemoveRange(index, count);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return nodes.GetEnumerator();
        }

        public int IndexOfContentNode(int startNodeIndex)
        {
            int depth = 1;
            for (int i = startNodeIndex + 1; i < Count; i++)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            return -1;
                        }

                        break;
                    case NodeType.Content:
                        if (depth == 1)
                        {
                            return i;
                        }

                        break;
                }
            }

            return -1;
        }

        public int IndexOfContentNode(EditNode startNode)
        {
            int startNodeIndex = IndexOf(startNode);
            if (startNodeIndex < 0)
            {
                return -1;
            }

            return IndexOfContentNode(startNodeIndex);
        }

        public int IndexOfEndNode(int startNodeIndex)
        {
            int depth = 1;
            for (int i = startNodeIndex + 1; i < Count; i++)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            return i;
                        }

                        break;
                }
            }

            return -1;
        }

        public int IndexOfEndNode(EditNode startNode)
        {
            int startNodeIndex = IndexOf(startNode);
            if (startNodeIndex < 0)
            {
                return -1;
            }

            return IndexOfEndNode(startNodeIndex);
        }

        public int IndexOfPropertyNode(int startNodeIndex, string name)
        {
            int depth = 1;
            for (int i = startNodeIndex + 1; i < Count; i++)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        if (depth == 1 && node.Name.Equals(name, StringComparison.Ordinal))
                        {
                            return i;
                        }

                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            return -1;
                        }

                        break;
                }
            }

            return -1;
        }

        public int IndexOfPropertyNode(EditNode startNode, string name)
        {
            int startNodeIndex = IndexOf(startNode);
            if (startNodeIndex < 0)
            {
                return -1;
            }

            return IndexOfPropertyNode(startNodeIndex, name);
        }

        public int IndexOfElementNode(int startNodeIndex, int elementIndex)
        {
            int currentElementIndex = -1;
            int depth = 1;
            for (int i = startNodeIndex + 1; i < Count; i++)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        if (depth == 1)
                        {
                            currentElementIndex++;
                            if (elementIndex == currentElementIndex)
                            {
                                return i;
                            }
                        }

                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            currentElementIndex++;
                            if (elementIndex == currentElementIndex)
                            {
                                return i;
                            }

                            return -1;
                        }

                        break;
                }
            }

            return -1;
        }

        public int IndexOfElementNode(EditNode startNode, int elementIndex)
        {
            int startNodeIndex = IndexOf(startNode);
            if (startNodeIndex < 0)
            {
                return -1;
            }

            return IndexOfElementNode(startNodeIndex, elementIndex);
        }

        public int IndexOfParentNode(int childNodeIndex)
        {
            int depth = 1;
            for (int i = childNodeIndex - 1; i >= 0; i--)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        depth--;
                        if (depth == 0)
                        {
                            return i;
                        }

                        break;
                    case NodeType.EndNode:
                        depth++;
                        break;
                }
            }

            return -1;
        }

        public int IndexOfParentNode(EditNode childNode)
        {
            int childNodeIndex = IndexOf(childNode);
            if (childNodeIndex < 0)
            {
                return -1;
            }

            return IndexOfParentNode(childNodeIndex);
        }

        public int CountElementNodes(int startNodeIndex)
        {
            int count = 0;
            int depth = 1;
            for (int i = startNodeIndex + 1; i < Count; i++)
            {
                INode node = nodes[i];
                switch (node.NodeType)
                {
                    case NodeType.StartNode:
                        if (depth == 1)
                        {
                            count++;
                        }

                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            return count;
                        }

                        break;
                }
            }

            return count;
        }

        public int CountElementNodes(EditNode startNode)
        {
            int startNodeIndex = IndexOf(startNode);
            if (startNodeIndex < 0)
            {
                return -1;
            }

            return CountElementNodes(startNodeIndex);
        }

        public List<EditNode> GetSubTree(int startNodeIndex)
        {
            List<EditNode> subTree = new List<EditNode>();
            int depth = 0;
            for (int i = startNodeIndex; i < Count; i++)
            {
                EditNode node = nodes[i];
                subTree.Add(node);
                switch (((INode)node).NodeType)
                {
                    case NodeType.StartNode:
                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            return subTree;
                        }

                        break;
                }
            }

            return subTree;
        }

        public void RemoveSubTree(int startNodeIndex)
        {
            List<EditNode> subTree = new List<EditNode>();
            int depth = 0;
            for (int i = startNodeIndex; i < Count; i++)
            {
                EditNode node = nodes[i];
                subTree.Add(node);
                switch (((INode)node).NodeType)
                {
                    case NodeType.StartNode:
                        depth++;
                        break;
                    case NodeType.EndNode:
                        depth--;
                        if (depth == 0)
                        {
                            RemoveRange(startNodeIndex, (i - startNodeIndex) + 1);
                            return;
                        }

                        break;
                }
            }
        }
    }
}